home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / system / news / inn1.000 / inn1.4sec-linux-src.tar / inn / expire / expireover.c < prev    next >
C/C++ Source or Header  |  1993-03-18  |  26KB  |  1,195 lines

  1. /*  $Revision: 1.3 $
  2. **
  3. **  Expire overview database.
  4. */
  5. #include "configdata.h"
  6. #include <stdio.h>
  7. #include <ctype.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include <sys/uio.h>
  11. #include <fcntl.h>
  12. #include <errno.h>
  13. #include "clibrary.h"
  14. #include "qio.h"
  15. #include "mydir.h"
  16. #include "libinn.h"
  17. #include "macros.h"
  18. #include "paths.h"
  19.  
  20.  
  21. #define    START_LIST_SIZE    128
  22.  
  23.  
  24. /*
  25. **   Information about a line in the overview file.
  26. */
  27. typedef struct _LINE {
  28.     ARTNUM    Article;
  29.     char    *Start;
  30.     int        Length;
  31.     int        Offset;
  32. } LINE;
  33.  
  34.  
  35. /*
  36. **  A list of articles; re-uses space.
  37. */
  38. typedef struct _LIST {
  39.     int        Used;
  40.     int        Size;
  41.     ARTNUM    *Articles;
  42. } LIST;
  43.  
  44.  
  45. /*
  46. **  A buffer; re-uses space.
  47. */
  48. typedef struct _BUFFER {
  49.     int        Used;
  50.     int        Size;
  51.     char    *Data;
  52. } BUFFER;
  53.  
  54.  
  55. /*
  56. **  Information about the schema of the news overview files.
  57. */
  58. typedef struct _ARTOVERFIELD {
  59.     char    *Header;
  60.     int        Length;
  61.     BOOL    HasHeader;
  62. } ARTOVERFIELD;
  63.  
  64.  
  65. /*
  66. **  Append an article to an LIST.
  67. */
  68. #define LISTappend(L, a)    \
  69.     if ((L).Size == (L).Used) {            \
  70.         (L).Size *= 2;                \
  71.         RENEW((L).Articles, ARTNUM, (L).Size);    \
  72.         (L).Articles[(L).Used++] = (a);        \
  73.     }                        \
  74.     else                        \
  75.         (L).Articles[(L).Used++] = (a)
  76.  
  77.  
  78. /*
  79. **  Global variables.
  80. */
  81. STATIC char        SPOOL[] = _PATH_SPOOL;
  82. STATIC char        *SCHEMA = _PATH_SCHEMA;
  83. STATIC BOOL        InSpoolDir;
  84. STATIC BOOL        Verbose;
  85. STATIC BOOL        DoNothing;
  86. STATIC ARTOVERFIELD    *ARTfields;
  87. STATIC int        ARTfieldsize;
  88.  
  89.  
  90. /*
  91. **  Sorting predicate for qsort to put articles in numeric order.
  92. */
  93. STATIC int
  94. LISTcompare(p1, p2)
  95.     POINTER    *p1;
  96.     POINTER    *p2;
  97. {
  98.     ARTNUM    *ip1;
  99.     ARTNUM    *ip2;
  100.  
  101.     ip1 = CAST(ARTNUM*, p1);
  102.     ip2 = CAST(ARTNUM*, p2);
  103.     return *ip1 - *ip2;
  104. }
  105.  
  106.  
  107. /*
  108. **  If list is big enough, and out of order, sort it.
  109. */
  110. STATIC void
  111. LISTsort(lp)
  112.     LIST    *lp;
  113. {
  114.     register int    i;
  115.     register ARTNUM    *ap;
  116.  
  117.     for (ap = lp->Articles, i = lp->Used - 1; --i >= 0; ap++)
  118.     if (ap[0] >= ap[1]) {
  119.         qsort((POINTER)lp->Articles, (SIZE_T)lp->Used,
  120.         sizeof lp->Articles[0], LISTcompare);
  121.         break;
  122.     }
  123. }
  124.  
  125.  
  126. /*
  127. **  Unlock the group.
  128. */
  129. STATIC void
  130. UnlockGroup(lfd, lockfile)
  131.     int        lfd;
  132.     char    *lockfile;
  133. {
  134.     if (lfd > 0) {
  135.     if (unlink(lockfile) < 0 && errno != ENOENT)
  136.         (void)fprintf(stderr, "expireover cant unlink %s %s\n",
  137.             lockfile, strerror(errno));
  138.     if (close(lfd) < 0)
  139.         (void)fprintf(stderr, "expireover cant close %s %s\n",
  140.             lockfile, strerror(errno));
  141.     lfd = -1;
  142.     }
  143. }
  144.  
  145.  
  146. /*
  147. **  Sorting predicate to put lines in numeric order.
  148. */
  149. STATIC int
  150. LINEcompare(p1, p2)
  151.     POINTER    *p1;
  152.     POINTER    *p2;
  153. {
  154.     LINE    *lp1;
  155.     LINE    *lp2;
  156.  
  157.     lp1 = CAST(LINE*, p1);
  158.     lp2 = CAST(LINE*, p2);
  159.     return lp1->Article - lp2->Article;
  160. }
  161.  
  162.  
  163. /*
  164. **  Take in a sorted list of count article numbers in group, and delete
  165. **  them from the overview file.
  166. */
  167. STATIC void
  168. RemoveLines(group, Deletes)
  169.     char            *group;
  170.     LIST            *Deletes;
  171. {
  172.     static BUFFER        B;
  173.     static LINE            *Lines;
  174.     static int            LineSize;
  175.     register struct iovec    *vp;
  176.     register LINE        *lp;
  177.     register LINE        *end;
  178.     register char        *p;
  179.     register char        *next;
  180.     register ARTNUM        *ap;
  181.     register int        i;
  182.     struct stat            Sb;
  183.     struct iovec        iov[8];
  184.     char            file[SPOOLNAMEBUFF];
  185.     char            lockfile[SPOOLNAMEBUFF];
  186.     int                fd;
  187.     int                count;
  188.     int                lfd;
  189.  
  190.     if (Verbose) {
  191.     for (ap = Deletes->Articles, i = Deletes->Used; --i >= 0; ap++)
  192.         (void)printf("- %s/%ld\n", group, *ap);
  193.     if (DoNothing)
  194.         return;
  195.     }
  196.  
  197.     /* Lock the group. */
  198.     (void)sprintf(lockfile, "%s/.LCK%s", group, _PATH_OVERVIEW);
  199.     lfd = open(lockfile, O_WRONLY | O_TRUNC | O_CREAT, ARTFILE_MODE);
  200.     if (lfd < 0) {
  201.     (void)fprintf(stderr, "Can't open %s, %s\n", lockfile, strerror(errno));
  202.     return;
  203.     }
  204.  
  205.     /* Open file, lock it. */
  206.     (void)sprintf(file, "%s/%s", group, _PATH_OVERVIEW);
  207.     for ( ; ; ) {
  208.     if ((fd = open(file, O_RDWR)) < 0) {
  209.         (void)fprintf(stderr, "Can't open %s, %s\n", file, strerror(errno));
  210.         UnlockGroup(lfd, lockfile);
  211.         return;
  212.     }
  213.     if (LockFile(fd, FALSE) >= 0)
  214.         break;
  215.     /* Wait for lock; close file -- might be unlinked -- and try again. */
  216.     (void)LockFile(fd, TRUE);
  217.     (void)close(fd);
  218.     }
  219.  
  220.     if (fstat(fd, &Sb) < 0) {
  221.     (void)fprintf(stderr, "Can't open %s, %s\n", file, strerror(errno));
  222.     UnlockGroup(lfd, lockfile);
  223.     (void)close(fd);
  224.     return;
  225.     }
  226.     if (Sb.st_size == 0) {
  227.     /* Empty file; done deleting. */
  228.     UnlockGroup(lfd, lockfile);
  229.     (void)close(fd);
  230.     return;
  231.     }
  232.  
  233.     /* Read in the whole file. */
  234.     if (B.Size == 0) {
  235.     B.Size = Sb.st_size + 1;
  236.     B.Data = NEW(char, B.Size);
  237.     }
  238.     else if (B.Size < Sb.st_size) {
  239.     B.Size = Sb.st_size + 1;
  240.     RENEW(B.Data, char, B.Size);
  241.     }
  242.     if (xread(fd, B.Data, Sb.st_size) < 0) {
  243.     (void)fprintf(stderr, "Can't read %s, %s\n", file, strerror(errno));
  244.     UnlockGroup(lfd, lockfile);
  245.     (void)close(fd);
  246.     return;
  247.     }
  248.     B.Data[Sb.st_size] = '\0';
  249.  
  250.     /* Count lines, get space. */
  251.     for (i = 1, p = B.Data; (p = strchr(p, '\n')) != NULL && *++p; i++)
  252.     continue;
  253.     if (LineSize == 0) {
  254.     LineSize = i;
  255.     Lines = NEW(LINE, LineSize + 1);
  256.     }
  257.     else if (LineSize < i) {
  258.     LineSize = i;
  259.     RENEW(Lines, LINE, LineSize + 1);
  260.     }
  261.  
  262.     /* Build line array. */
  263.     for (lp = Lines, p = B.Data; ; p = next, lp++) {
  264.     if ((next = strchr(p, '\n')) == NULL)
  265.         break;
  266.     lp->Start = p;
  267.     lp->Length = ++next - p;
  268.     lp->Article = atol(p);
  269.     }
  270.     qsort((POINTER)Lines, (SIZE_T)(lp - Lines), sizeof lp[0], LINEcompare);
  271.  
  272.     /* Remove duplicates. */
  273.     for (end = lp - 1, lp = Lines; lp < end; lp++)
  274.     if (lp[0].Article == lp[1].Article)
  275.         lp->Article = 0;
  276.  
  277.     /* Scan through lines, collecting clumps and skipping holes. */
  278.     ap = Deletes->Articles;
  279.     count = Deletes->Used;
  280.     iov[0].iov_len = 0;
  281.     for (vp = iov, lp = Lines; lp < end + 1; lp++) {
  282.     /* An already-removed article, or one that should be? */
  283.     if (lp->Article == 0)
  284.         continue;
  285.  
  286.     /* Skip delete items before the current one. */
  287.     while (*ap < lp->Article && count > 0) {
  288.         ap++;
  289.         count--;
  290.     }
  291.  
  292.     if (count > 0 && lp->Article == *ap) {
  293.         while (*ap == lp->Article && count > 0) {
  294.         ap++;
  295.         count--;
  296.         }
  297.         continue;
  298.     }
  299.  
  300.     /* We're keeping this entry; see if we can add it to any
  301.      * in-progress iov element. */
  302.     if (vp->iov_len) {
  303.         if (vp->iov_base + vp->iov_len == lp->Start) {
  304.         /* Contiguous. */
  305.         vp->iov_len += lp->Length;
  306.         continue;
  307.         }
  308.  
  309.         /* Doesn't fit -- get a new element. */
  310.         if (++vp == ENDOF(iov)) {
  311.         if (xwritev(lfd, iov, SIZEOF(iov)) < 0) {
  312.             (void)fprintf(stderr, "Can't write %s, %s\n",
  313.                 lockfile, strerror(errno));
  314.             UnlockGroup(lfd, lockfile);
  315.             (void)close(fd);
  316.             return;
  317.         }
  318.         vp = iov;
  319.         }
  320.     }
  321.  
  322.     /* Start new element. */
  323.     vp->iov_base = lp->Start;
  324.     vp->iov_len = lp->Length;
  325.     }
  326.  
  327.     /* Write out remaining. */
  328.     if (vp->iov_len)
  329.     vp++;
  330.     if (iov[0].iov_len && xwritev(lfd, iov, vp - iov) < 0) {
  331.     (void)fprintf(stderr, "Can't write %s, %s\n",
  332.         lockfile, strerror(errno));
  333.     UnlockGroup(lfd, lockfile);
  334.     (void)close(fd);
  335.     return;
  336.     }
  337.  
  338.     if (rename(lockfile, file) < 0)
  339.     (void)fprintf(stderr, "Can't rename %s, %s\n",
  340.         lockfile, strerror(errno));
  341.  
  342.     /* Don't call UnlockGroup; do it inline. */
  343.     if (close(lfd) < 0)
  344.     (void)fprintf(stderr, "expireover cant close %s %s\n",
  345.         file, strerror(errno));
  346.     if (close(fd) < 0)
  347.     (void)fprintf(stderr, "expireover cant close unlinked %s %s\n",
  348.         file, strerror(errno));
  349. }
  350.  
  351.  
  352. /*
  353. **  Read the overview schema.
  354. */
  355. void
  356. ARTreadschema()
  357. {
  358.     register FILE        *F;
  359.     register char        *p;
  360.     register ARTOVERFIELD    *fp;
  361.     register int        i;
  362.     char            buff[SMBUF];
  363.  
  364.     /* Open file, count lines. */
  365.     if ((F = fopen(SCHEMA, "r")) == NULL) {
  366.     (void)fprintf(stderr, "Can't open %s, %s\n", SCHEMA, strerror(errno));
  367.     exit(1);
  368.     }
  369.     for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
  370.     continue;
  371.     (void)fseek(F, (OFFSET_T)0, SEEK_SET);
  372.     ARTfields = NEW(ARTOVERFIELD, i + 1);
  373.  
  374.     /* Parse each field. */
  375.     for (fp = ARTfields; fgets(buff, sizeof buff, F) != NULL; ) {
  376.     /* Ignore blank and comment lines. */
  377.     if ((p = strchr(buff, '\n')) != NULL)
  378.         *p = '\0';
  379.     if ((p = strchr(buff, COMMENT_CHAR)) != NULL)
  380.         *p = '\0';
  381.     if (buff[0] == '\0')
  382.         continue;
  383.     if ((p = strchr(buff, ':')) != NULL) {
  384.         *p++ = '\0';
  385.         fp->HasHeader = EQ(p, "full");
  386.     }
  387.     else
  388.         fp->HasHeader = FALSE;
  389.     fp->Header = COPY(buff);
  390.     fp->Length = strlen(buff);
  391.     fp++;
  392.     }
  393.     ARTfieldsize = fp - ARTfields;
  394.     (void)fclose(F);
  395. }
  396.  
  397.  
  398. /*
  399. **  Read an article and create an overview line without the trailing
  400. **  newline.  Returns pointer to static space or NULL on error.
  401. */
  402. STATIC char *
  403. OVERgen(name)
  404.     char            *name;
  405. {
  406.     static ARTOVERFIELD        *Headers;
  407.     static BUFFER        B;
  408.     register ARTOVERFIELD    *fp;
  409.     register ARTOVERFIELD    *hp;
  410.     register QIOSTATE        *qp;
  411.     register char        *colon;
  412.     register char        *line;
  413.     register char        *p;
  414.     register int        i;
  415.     register int        size;
  416.     register int        ov_size;
  417.     register long        lines;
  418.     struct stat            Sb;
  419.     long            t;
  420.     char            value[10];
  421.  
  422.     /* Open article. */
  423.     if ((qp = QIOopen(name, QIO_BUFFER)) == NULL)
  424.     return NULL;
  425.     if ((p = strrchr(name, '/')) != NULL)
  426.     name = p + 1;
  427.  
  428.     /* Set up place to store headers. */
  429.     if (Headers == NULL) {
  430.     Headers = NEW(ARTOVERFIELD, ARTfieldsize);
  431.     for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++)
  432.         hp->Length = 0;
  433.     }
  434.     for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++)
  435.     hp->HasHeader = FALSE;
  436.  
  437.     for ( ; ; ) {
  438.     /* Read next line. */
  439.     if ((line = QIOread(qp)) == NULL) {
  440.         if (QIOtoolong(qp))
  441.         continue;
  442.         /* Error or EOF (in headers!?); shouldn't happen. */
  443.         QIOclose(qp);
  444.         return NULL;
  445.     }
  446.  
  447.     /* End of headers? */
  448.     if (*line == '\0')
  449.         break;
  450.  
  451.     /* See if we want this header. */
  452.     fp = ARTfields;
  453.     for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++, fp++) {
  454.         colon = &line[fp->Length];
  455.         if (*colon != ':')
  456.         continue;
  457.         *colon = '\0';
  458.         if (!caseEQ(line, fp->Header)) {
  459.         *colon = ':';
  460.         continue;
  461.         }
  462.         *colon = ':';
  463.         if (fp->HasHeader)
  464.         p = line;
  465.         else
  466.         /* Skip colon and whitespace, store value. */
  467.         for (p = colon; *++p && ISWHITE(*p); )
  468.             continue;
  469.         size = strlen(p);
  470.         if (hp->Length == 0) {
  471.         hp->Length = size;
  472.         hp->Header = NEW(char, hp->Length + 1);
  473.         }
  474.         else if (hp->Length < size) {
  475.         hp->Length = size;
  476.         RENEW(hp->Header, char, hp->Length + 1);
  477.         }
  478.         (void)strcpy(hp->Header, p);
  479.         for (p = hp->Header; *p; p++)
  480.         if (*p == '\t' || *p == '\n')
  481.             *p = ' ';
  482.         hp->HasHeader = TRUE;
  483.     }
  484.     }
  485.  
  486.     /* Read body of article, just to get lines. */
  487.     for (lines = 0; ; lines++)
  488.     if ((p = QIOread(qp)) == NULL) {
  489.         if (QIOtoolong(qp))
  490.         continue;
  491.         if (QIOerror(qp)) {
  492.         QIOclose(qp);
  493.         return NULL;
  494.         }
  495.         break;
  496.     }
  497.  
  498.     /* Calculate total size, fix hardwired headers. */
  499.     ov_size = strlen(name) + ARTfieldsize + 2;
  500.     for (hp = Headers, fp = ARTfields, i = ARTfieldsize; --i >= 0; hp++, fp++) {
  501.     if (caseEQ(fp->Header, "Bytes") || caseEQ(fp->Header, "Lines")) {
  502.         if (fp->Header[0] == 'B' || fp->Header[0] == 'b')
  503.         t = fstat(QIOfileno(qp), &Sb) >= 0 ? (long)Sb.st_size : 0L;
  504.         else
  505.         t = lines;
  506.  
  507.         (void)sprintf(value, "%ld", t);
  508.         size = strlen(value);
  509.         if (hp->Length == 0) {
  510.          hp->Length = size;
  511.         hp->Header = NEW(char, hp->Length + 1);
  512.         }
  513.         else if (hp->Length < size) {
  514.         hp->Length = size;
  515.         RENEW(hp->Header, char, hp->Length + 1);
  516.         }
  517.         (void)strcpy(hp->Header, value);
  518.         hp->HasHeader = TRUE;
  519.        }
  520.        if (hp->HasHeader)
  521.        ov_size += strlen(hp->Header);
  522.     }
  523.  
  524.     /* Get space. */
  525.     if (B.Size == 0) {
  526.     B.Size = ov_size;
  527.     B.Data = NEW(char, B.Size + 1);
  528.     }
  529.     else if (B.Size < ov_size) {
  530.     B.Size = ov_size;
  531.     RENEW(B.Data, char, B.Size + 1);
  532.     }
  533.  
  534.     /* Glue all the fields together. */
  535.     p = B.Data + strlen(strcpy(B.Data, name));
  536.     for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++) {
  537.     *p++ = '\t';
  538.     if (hp->HasHeader)
  539.         p += strlen(strcpy(p, hp->Header));
  540.     }
  541.     *p = '\0';
  542.  
  543.     QIOclose(qp);
  544.     return B.Data;
  545. }
  546.  
  547.  
  548. /*
  549. **  Take in a sorted list of count article numbers in group, and add
  550. **  them them to the overview file.
  551. */
  552. STATIC void
  553. AddLines(group, Adds)
  554.     char            *group;
  555.     LIST            *Adds;
  556. {
  557.     static BUFFER        New;
  558.     static BUFFER        B;
  559.     static LINE            *Lines;
  560.     static int            LineSize;
  561.     register LINE        *lp;
  562.     register char        *next;
  563.     register int        i;
  564.     register struct iovec    *vp;
  565.     register ARTNUM        *ap;
  566.     LINE            *end;
  567.     struct iovec        iov[8];
  568.     struct stat            Sb;
  569.     char            *p;
  570.     char            file[SPOOLNAMEBUFF];
  571.     char            lockfile[SPOOLNAMEBUFF];
  572.     int                LineUsed;
  573.     int                fd;
  574.     int                lfd;
  575.  
  576.     if (Verbose) {
  577.     for (ap = Adds->Articles, i = Adds->Used; --i >= 0; ap++)
  578.         (void)printf("+ %s/%ld\n", group, *ap);
  579.     if (DoNothing)
  580.         return;
  581.     }
  582.  
  583.     /* Get space. */
  584.     if (New.Data == NULL) {
  585.     New.Size = 1024;
  586.     New.Data = NEW(char, New.Size);
  587.     LineSize = Adds->Size + 1;
  588.     Lines = NEW(LINE, LineSize);
  589.     }
  590.     else if (LineSize < Adds->Size) {
  591.     LineSize = Adds->Size + 1;
  592.     RENEW(Lines, LINE, LineSize);
  593.     }
  594.  
  595.     New.Used = 0;
  596.     for (lp = Lines, ap = Adds->Articles, i = Adds->Used; --i >= 0; ap++) {
  597.     /* Get the overview data. */
  598.     if (InSpoolDir)
  599.         (void)sprintf(file, "%s/%ld", group, *ap);
  600.     else
  601.         (void)sprintf(file, "%s/%s/%ld", SPOOL, group, *ap);
  602.     if ((p = OVERgen(file)) == NULL)
  603.         continue;
  604.  
  605.     /* Add it to the buffer and the lines array. */
  606.     lp->Article = *ap;
  607.     lp->Length = strlen(p);
  608.     lp->Offset = New.Used;
  609.     while (New.Size < New.Used + lp->Length + 1) {
  610.         New.Size *= 2;
  611.         RENEW(New.Data, char, New.Size);
  612.     }
  613.     (void)strcpy(&New.Data[New.Used], p);
  614.     New.Used += lp->Length++;
  615.     New.Data[New.Used++] = '\n';
  616.     lp++;
  617.     }
  618.     LineUsed = lp - Lines;
  619.  
  620.     /* Turn offsets into real pointers. */
  621.     for (i = 0, lp = Lines; i < LineUsed; i++, lp++)
  622.     lp->Start = New.Data + lp->Offset;
  623.  
  624.     /* Lock the group. */
  625.     (void)sprintf(lockfile, "%s/.LCK%s", group, _PATH_OVERVIEW);
  626.     lfd = open(lockfile, O_WRONLY | O_TRUNC | O_CREAT, ARTFILE_MODE);
  627.     if (lfd < 0) {
  628.     (void)fprintf(stderr, "Can't open %s, %s\n", lockfile, strerror(errno));
  629.     return;
  630.     }
  631.  
  632.     /* Open file, lock it. */
  633.     (void)sprintf(file, "%s/%s", group, _PATH_OVERVIEW);
  634.     for ( ; ; ) {
  635.     if ((fd = open(file, O_RDWR | O_CREAT, ARTFILE_MODE)) < 0) {
  636.         (void)fprintf(stderr, "Can't open %s, %s\n", file, strerror(errno));
  637.         UnlockGroup(lfd, lockfile);
  638.         return;
  639.     }
  640.     if (LockFile(fd, FALSE) >= 0)
  641.         break;
  642.     /* Wait for lock; close file -- might be unlinked -- and try again. */
  643.     (void)LockFile(fd, TRUE);
  644.     (void)close(fd);
  645.     }
  646.  
  647.     if (fstat(fd, &Sb) < 0) {
  648.     (void)fprintf(stderr, "Can't open %s, %s\n", file, strerror(errno));
  649.     UnlockGroup(lfd, lockfile);
  650.     (void)close(fd);
  651.     return;
  652.     }
  653.  
  654.     if (Sb.st_size != 0) {
  655.     /* Read in the whole file. */
  656.     if (B.Size == 0) {
  657.         B.Size = Sb.st_size + 1;
  658.         B.Data = NEW(char, B.Size);
  659.     }
  660.     else if (B.Size < Sb.st_size) {
  661.         B.Size = Sb.st_size + 1;
  662.         RENEW(B.Data, char, B.Size);
  663.     }
  664.     if (xread(fd, B.Data, Sb.st_size) < 0) {
  665.         (void)fprintf(stderr, "Can't read %s, %s\n",
  666.             file, strerror(errno));
  667.         UnlockGroup(lfd, lockfile);
  668.         (void)close(fd);
  669.         return;
  670.     }
  671.     B.Data[Sb.st_size] = '\0';
  672.  
  673.     /* Count lines, get space. */
  674.     for (i = 1, p = B.Data; (p = strchr(p, '\n')) != NULL && *++p; i++)
  675.         continue;
  676.     if (LineSize < i + 1 + LineUsed) {
  677.         LineSize = i + 1 + LineUsed;
  678.         RENEW(Lines, LINE, LineSize);
  679.     }
  680.  
  681.     /* Add to lines array. */
  682.     for (lp = Lines + LineUsed, p = B.Data; ; p = next, lp++) {
  683.         if ((next = strchr(p, '\n')) == NULL)
  684.         break;
  685.         lp->Start = p;
  686.         lp->Length = ++next - p;
  687.         lp->Article = atol(p);
  688.     }
  689.     qsort((POINTER)Lines, (SIZE_T)(lp - Lines), sizeof lp[0],
  690.         LINEcompare);
  691.     LineUsed = lp - Lines;
  692.     }
  693.  
  694.     /* Remove duplicates. */
  695.     for (end = lp - 1, lp = Lines; lp < end; lp++)
  696.     if (lp[0].Article == lp[1].Article)
  697.         lp->Article = 0;
  698.  
  699.     /* Scan through lines, collecting rocks and holes. */
  700.     iov[0].iov_len = 0;
  701.     for (vp = iov, lp = Lines; lp < end + 1; lp++) {
  702.     /* An already-removed article, or one that should be? */
  703.     if (lp->Article == 0)
  704.         continue;
  705.  
  706.     /* We're keeping this entry; see if we can add it to any in-progress
  707.      * iov element. */
  708.     if (vp->iov_len) {
  709.         if (vp->iov_base + vp->iov_len == lp->Start) {
  710.         /* Contiguous. */
  711.         vp->iov_len += lp->Length;
  712.         continue;
  713.         }
  714.  
  715.         /* Doesn't fit -- get a new element. */
  716.         if (++vp == ENDOF(iov)) {
  717.         if (xwritev(lfd, iov, SIZEOF(iov)) < 0) {
  718.             (void)fprintf(stderr, "Can't write %s, %s\n",
  719.                 lockfile, strerror(errno));
  720.             UnlockGroup(lfd, lockfile);
  721.             (void)close(fd);
  722.             return;
  723.         }
  724.         vp = iov;
  725.         }
  726.     }
  727.  
  728.     /* Start new element. */
  729.     vp->iov_base = lp->Start;
  730.     vp->iov_len = lp->Length;
  731.     }
  732.  
  733.     if (vp->iov_len)
  734.     vp++;
  735.  
  736.     /* Write out remaining. */
  737.     if (iov[0].iov_len && xwritev(lfd, iov, vp - iov) < 0) {
  738.     (void)fprintf(stderr, "Can't write %s, %s\n",
  739.                lockfile, strerror(errno));
  740.     UnlockGroup(lfd, lockfile);
  741.     (void)close(fd);
  742.     return;
  743.     }
  744.  
  745.     if (rename(lockfile, file) < 0)
  746.     (void)fprintf(stderr, "Can't rename %s, %s\n",
  747.                lockfile, strerror(errno));
  748.  
  749.     /* Don't call UnlockGroup; do it inline. */
  750.     if (close(lfd) < 0)
  751.     (void)fprintf(stderr, "expireover cant close %s %s\n",
  752.         file, strerror(errno));
  753.     if (close(fd) < 0)
  754.     (void)fprintf(stderr, "expireover cant close unlinked %s %s\n",
  755.         file, strerror(errno));
  756. }
  757.  
  758.  
  759. /*
  760. **  Expire by batch, or line at a time.
  761. */
  762. STATIC void
  763. Expire(SortedInput, qp)
  764.     BOOL        SortedInput;
  765.     register QIOSTATE    *qp;
  766. {
  767.     static LIST        List;
  768.     register char    *line;
  769.     register char    *p;
  770.     char        group[SPOOLNAMEBUFF];
  771.  
  772.     if (List.Articles == NULL) {
  773.     List.Size = START_LIST_SIZE;
  774.     List.Articles = NEW(ARTNUM, List.Size);
  775.     }
  776.     List.Used = 0;
  777.  
  778.     if (SortedInput) {
  779.     for ( ; ; ) {
  780.         if ((line = QIOread(qp)) == NULL) {
  781.         if (QIOerror(qp)) {
  782.             (void)fprintf(stderr, "Can't read input %s\n",
  783.                 strerror(errno));
  784.             break;
  785.         }
  786.         if (QIOtoolong(qp))
  787.             continue;
  788.         break;
  789.         }
  790.         if ((p = strrchr(line, '/')) == NULL)
  791.         continue;
  792.         *p++ = '\0';
  793.         if (List.Used == 0) {
  794.         (void)strcpy(group, line);
  795.         List.Used = 0;
  796.         }
  797.         else if (!EQ(line, group)) {
  798.         LISTsort(&List);
  799.         RemoveLines(group, &List);
  800.         (void)strcpy(group, line);
  801.         List.Used = 0;
  802.         }
  803.         LISTappend(List, atol(p));
  804.     }
  805.  
  806.     /* Do the last group. */
  807.     if (List.Used) {
  808.         LISTsort(&List);
  809.         RemoveLines(group, &List);
  810.     }
  811.     }
  812.     else {
  813.     for (List.Used = 1; ; ) {
  814.         if ((line = QIOread(qp)) == NULL) {
  815.         if (QIOerror(qp)) {
  816.             (void)fprintf(stderr, "Can't read input %s\n",
  817.                 strerror(errno));
  818.             break;
  819.         }
  820.         if (QIOtoolong(qp))
  821.             continue;
  822.         break;
  823.         }
  824.         if ((p = strrchr(line, '/')) == NULL)
  825.         continue;
  826.         *p++ = '\0';
  827.         List.Articles[0] = atol(p);
  828.         RemoveLines(line, &List);
  829.     }
  830.     }
  831.  
  832.     QIOclose(qp);
  833. }
  834.  
  835.  
  836. /*
  837. **  Read the overview file, return sorted list of all articles in it.
  838. */
  839. STATIC LIST *
  840. GetOverviewList(group)
  841.     char        *group;
  842. {
  843.     static LIST        List;
  844.     register QIOSTATE    *qp;
  845.     register char    *p;
  846.     char        file[SPOOLNAMEBUFF];
  847.  
  848.     /* Open the file. */
  849.     (void)sprintf(file, "%s/%s", group, _PATH_OVERVIEW);
  850.     if ((qp = QIOopen(file, QIO_BUFFER)) == NULL)
  851.     return NULL;
  852.  
  853.     /* Setup the article list. */
  854.     if (List.Articles == NULL) {
  855.     List.Size = START_LIST_SIZE;
  856.     List.Articles = NEW(ARTNUM, List.Size);
  857.     }
  858.     List.Used = 0;
  859.  
  860.     /* Read all lines, picking up the article number. */
  861.     for ( ; ; ) {
  862.     if ((p = QIOread(qp)) == NULL) {
  863.         if (QIOerror(qp)) {
  864.         (void)fprintf(stderr, "Can't read %s, %s\n",
  865.             file, strerror(errno));
  866.         QIOclose(qp);
  867.         return NULL;
  868.         }
  869.         if (QIOtoolong(qp))
  870.         continue;
  871.         break;
  872.     }
  873.     LISTappend(List, atol(p));
  874.     }
  875.     QIOclose(qp);
  876.  
  877.     if (List.Used == 0)
  878.     return NULL;
  879.     LISTsort(&List);
  880.     return &List;
  881. }
  882.  
  883.  
  884. /*
  885. **  Read spool directory and return sorted list of articles or NULL on error.
  886. */
  887. STATIC LIST *
  888. GetSpoolList(group)
  889.     char        *group;
  890. {
  891.     static LIST        List;
  892.     register DIR    *dp;
  893.     register DIRENTRY    *ep;
  894.     register char    *p;
  895.     char        buff[SPOOLNAMEBUFF];
  896.  
  897.     /* Open directory. */
  898.     if (InSpoolDir)
  899.     (void)strcpy(buff, group);
  900.     else
  901.     (void)sprintf(buff, "%s/%s", SPOOL, group);
  902.     if ((dp = opendir(buff)) == NULL)
  903.     return NULL;
  904.  
  905.     /* Setup article list. */
  906.     if (List.Articles == NULL) {
  907.     List.Size = START_LIST_SIZE;
  908.     List.Articles = NEW(ARTNUM, List.Size);
  909.     }
  910.     List.Used = 0;
  911.  
  912.     /* Get all articles. */
  913.     while ((ep = readdir(dp)) != NULL) {
  914.     p = ep->d_name;
  915.     if (!CTYPE(isdigit, p[0]) || strspn(p, "0123456789") != strlen(p))
  916.         continue;
  917.     LISTappend(List, atol(p));
  918.     }
  919.     (void)closedir(dp);
  920.  
  921.     if (List.Used == 0)
  922.     return NULL;
  923.     LISTsort(&List);
  924.     return &List;
  925. }
  926.  
  927.  
  928. /*
  929. **  Return a list of all articles in the Over list that are not in
  930. **  the Spool list.  Both lists are sorted.  See SpoolUpdate for an
  931. **  explanation of the names.
  932. */
  933. STATIC LIST *
  934. GetNotIn(Over, Spool)
  935.     register LIST    *Over;
  936.     register LIST    *Spool;
  937. {
  938.     static LIST        List;
  939.     register ARTNUM    *oEnd;
  940.     register ARTNUM    *sEnd;
  941.     register ARTNUM    *o;
  942.     register ARTNUM    *s;
  943.  
  944.     /* Setup the list. */
  945.     if (List.Articles == NULL) {
  946.     List.Size = START_LIST_SIZE;
  947.     List.Articles = NEW(ARTNUM, List.Size);
  948.     }
  949.     List.Used = 0;
  950.  
  951.     o = Over->Articles;
  952.     s = Spool->Articles;
  953.     oEnd = o + Over->Used;
  954.     sEnd = s + Spool->Used;
  955.     while (o != oEnd && s != sEnd) {
  956.     if (*o < *s) {
  957.         LISTappend(List, *o++);
  958.         continue;
  959.     }
  960.     if (*o == *s)
  961.         o++;
  962.     s++;
  963.     }
  964.  
  965.     /* If we hit the end of the Spool, then add everything else in the
  966.      * Overview. */
  967.     if (s == sEnd) {
  968.     while (o != oEnd)
  969.         LISTappend(List, *o++);
  970.     }
  971.  
  972.     return List.Used ? &List : NULL;
  973. }
  974.  
  975.  
  976. /*
  977. **  Try to make one directory.  Return FALSE on error.
  978. */
  979. STATIC BOOL
  980. MakeDir(Name)
  981.     char    *Name;
  982. {
  983.     struct stat    Sb;
  984.  
  985.     if (mkdir(Name, GROUPDIR_MODE) >= 0)
  986.     return TRUE;
  987.  
  988.     /* See if it failed because it already exists. */
  989.     return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode);
  990. }
  991.  
  992.  
  993. /*
  994. **  Given a directory, comp/foo/bar, create that directory and all
  995. **  intermediate directories needed.  Return FALSE on error.
  996. */
  997. BOOL
  998. MakeOverDir(Name)
  999.     register char    *Name;
  1000. {
  1001.     register char    *p;
  1002.     BOOL        made;
  1003.  
  1004.     /* Optimize common case -- parent almost always exists. */
  1005.     if (MakeDir(Name))
  1006.     return TRUE;
  1007.  
  1008.     /* Try to make each of comp and comp/foo in turn. */
  1009.     for (p = Name; *p; p++)
  1010.     if (*p == '/') {
  1011.         *p = '\0';
  1012.         made = MakeDir(Name);
  1013.         *p = '/';
  1014.         if (!made)
  1015.         return FALSE;
  1016.     }
  1017.  
  1018.     return MakeDir(Name);
  1019. }
  1020.  
  1021.  
  1022. /*
  1023. **  Update using the News Spool.  Either add or delete entries.
  1024. */
  1025. STATIC void
  1026. SpoolUpdate(AddEntries, Name)
  1027.     BOOL        AddEntries;
  1028.     char        *Name;
  1029. {
  1030.     register QIOSTATE    *qp;
  1031.     register char    *line;
  1032.     register char    *p;
  1033.     LIST        *Over;
  1034.     LIST        *Spool;
  1035.     LIST        *Missing;
  1036.  
  1037.     /* Open file. */
  1038.     if (EQ(Name, "-"))
  1039.     qp = QIOfdopen(STDIN, QIO_BUFFER);
  1040.     else if ((qp = QIOopen(Name, QIO_BUFFER)) == NULL) {
  1041.     (void)fprintf(stderr, "Can't open %s, %s\n", Name, strerror(errno));
  1042.     exit(1);
  1043.     }
  1044.     if (AddEntries)
  1045.     ARTreadschema();
  1046.  
  1047.     for ( ; ; ) {
  1048.     if ((line = QIOread(qp)) == NULL) {
  1049.         if (QIOtoolong(qp) || QIOerror(qp)) {
  1050.         (void)fprintf(stderr,
  1051.             "Line too long or error reading %s, %s\n",
  1052.             Name, strerror(errno));
  1053.         exit(1);
  1054.         }
  1055.         break;
  1056.     }
  1057.  
  1058.     /* Nip off newsgroup name, and turn it into a directory. */
  1059.     for (p = line; *p && !ISWHITE(*p) && *p != '\n'; p++)
  1060.         if (*p == '.')
  1061.         *p = '/';
  1062.     *p = '\0';
  1063.  
  1064.     if (AddEntries) {
  1065.         if ((Spool = GetSpoolList(line)) == NULL)
  1066.         continue;
  1067.         if ((Over = GetOverviewList(line)) != NULL) {
  1068.         if ((Missing = GetNotIn(Spool, Over)) != NULL)
  1069.             AddLines(line, Missing);
  1070.         }
  1071.         else if (!InSpoolDir) {
  1072.         if (MakeOverDir(line))
  1073.             AddLines(line, Spool);
  1074.         else
  1075.             (void)fprintf(stderr, "expireover: cant mkdir %s, %s\n",
  1076.                 line, strerror(errno));
  1077.         }
  1078.         else
  1079.         AddLines(line, Spool);
  1080.         continue;
  1081.     }
  1082.  
  1083.     if ((Over = GetOverviewList(line)) == NULL)
  1084.         continue;
  1085.     if ((Spool = GetSpoolList(line)) != NULL) {
  1086.         if ((Missing = GetNotIn(Over, Spool)) != NULL)
  1087.         RemoveLines(line, Missing);
  1088.     }
  1089.     else
  1090.         RemoveLines(line, Over);
  1091.     }
  1092.  
  1093.     QIOclose(qp);
  1094.     exit(0);
  1095. }
  1096.  
  1097.  
  1098.  
  1099. /*
  1100. **  Print usage message and exit.
  1101. */
  1102. STATIC NORETURN
  1103. Usage()
  1104. {
  1105.     (void)fprintf(stderr, "Usage:  expireover [flags] [file...]\n");
  1106.     exit(1);
  1107. }
  1108.  
  1109.  
  1110. int
  1111. main(ac, av)
  1112.     int            ac;
  1113.     char        *av[];
  1114. {
  1115.     register int    i;
  1116.     QIOSTATE        *qp;
  1117.     BOOL        AddEntries;
  1118.     BOOL        ReadSpool;
  1119.     BOOL        SortedInput;
  1120.     char        *Dir;
  1121.     char        *Name;
  1122.  
  1123.     /* Set defaults. */
  1124.     Dir = _PATH_OVERVIEWDIR;
  1125.     Name = _PATH_ACTIVE;
  1126.     AddEntries = FALSE;
  1127.     ReadSpool = FALSE;
  1128.     SortedInput = FALSE;
  1129.     (void)umask(NEWSUMASK);
  1130.  
  1131.     /* Parse JCL. */
  1132.     while ((i = getopt(ac, av, "aD:f:nO:svz")) != EOF)
  1133.     switch (i) {
  1134.     default:
  1135.         Usage();
  1136.         /* NOTREACHED */
  1137.     case 'a':
  1138.         AddEntries = TRUE;
  1139.         ReadSpool = TRUE;
  1140.         break;
  1141.     case 'D':
  1142.         Dir = optarg;
  1143.         break;
  1144.     case 'f':
  1145.         Name = optarg;
  1146.         break;
  1147.     case 'n':
  1148.         DoNothing = TRUE;
  1149.         break;
  1150.     case 'O':
  1151.         SCHEMA = optarg;
  1152.         break;
  1153.     case 's':
  1154.         ReadSpool = TRUE;
  1155.         break;
  1156.     case 'v':
  1157.         Verbose = TRUE;
  1158.         break;
  1159.     case 'z':
  1160.         SortedInput = TRUE;
  1161.         break;
  1162.     }
  1163.     ac -= optind;
  1164.     av += optind;
  1165.     if ((ReadSpool && ac) || (AddEntries && !ReadSpool))
  1166.     Usage();
  1167.  
  1168.     /* Setup. */
  1169.     if (chdir(Dir) < 0) {
  1170.     (void)fprintf(stderr, "Cant chdir to %s, %s\n", Dir, strerror(errno));
  1171.     exit(1);
  1172.     }
  1173.     InSpoolDir = EQ(Dir, SPOOL);
  1174.  
  1175.     /* Do work. */
  1176.     if (ReadSpool)
  1177.     SpoolUpdate(AddEntries, Name);
  1178.     if (ac == 0)
  1179.     Expire(SortedInput, QIOfdopen(STDIN, QIO_BUFFER));
  1180.     else {
  1181.     for ( ; *av; av++)
  1182.         if (EQ(*av, "-"))
  1183.         Expire(SortedInput, QIOfdopen(STDIN, QIO_BUFFER));
  1184.         else if ((qp = QIOopen(*av, QIO_BUFFER)) == NULL)
  1185.         (void)fprintf(stderr, "Can't open %s, %s\n",
  1186.             *av, strerror(errno));
  1187.         else
  1188.         Expire(SortedInput, qp);
  1189.     }
  1190.  
  1191.     exit(0);
  1192.     /* NOTREACHED */
  1193. }
  1194.  
  1195.